package adapter

import (
	"bytes"
	"context"
	"database/sql"
	"errors"
	"fmt"
	"github.com/casbin/casbin/v2/model"
	"github.com/casbin/casbin/v2/persist"
	"github.com/jmoiron/sqlx"
	"strconv"
)

const (
	defaultDatabaseName = "casbin"
	defaultTableName 	= "casbin_rule"
)

// 需要映射到数据库casbin_rule表的 orm模型，操作这个model就是操作casbin_rule表
type CasbinRule struct {
	ID    uint   `db:"id"`
	PType string `db:"p_type"`
	V0    string `db:"v0"`
	V1    string `db:"v1"`
	V2    string `db:"v2"`
	V3    string `db:"v3"`
	V4    string `db:"v4"`
	V5    string `db:"v5"`
}

// Casbin的数据库适配器
type Adapter struct {
	db               *sqlx.DB
	ctx              context.Context

	tableName        string
	createTableSql   string
	truncateTableSql string
	tableExistSql    string
	insertRowSql     string
	deleteAllSql     string
	deleteByArgSql   string
	selectAllSql     string
	selectWhereSql   string
	isFiltered       bool
}
// Casbin 为FilteredAdapter的策略定义过滤规则。空值将被忽略,但所有其他值必须与过滤器匹配。
type Filter struct {
	PType []string
	V0    []string
	V1    []string
	V2    []string
	V3    []string
	V4    []string
	V5    []string
}

// 初始化 Adapter (Adapter保存的是一些操作 Casbin_rule表的SQL语句!
func (p *Adapter)initAdapter(){
	p.createTableSql = fmt.Sprintf(createTable,p.tableName)
	p.truncateTableSql = fmt.Sprintf(TruncateTable,p.tableName)

	p.tableExistSql = fmt.Sprintf(IsTableExist,p.tableName)

	p.insertRowSql = fmt.Sprintf(InsertRow,p.tableName)
	p.deleteAllSql = fmt.Sprintf(DeleteAll,p.tableName)
	p.deleteByArgSql = fmt.Sprintf(DeleteByArg,p.tableName)

	p.selectAllSql = fmt.Sprintf(SelectAll,p.tableName)
	p.selectWhereSql = fmt.Sprintf(SelectWhere,p.tableName)

	switch p.db.DriverName(){
	case "postgres", "pgx", "pq-timeouts", "cloudsqlpostgres":
		p.createTableSql = fmt.Sprintf(createTablePostgres, p.tableName)
		p.insertRowSql = fmt.Sprintf(insertRowPostgres, p.tableName)
	case "mysql":
		p.createTableSql = fmt.Sprintf(createTableMysql, p.tableName)
	case "sqlite3":
		p.createTableSql = fmt.Sprintf(createTableSqlite3, p.tableName)
		p.truncateTableSql = fmt.Sprintf(truncateTableSqlite3, p.tableName)
	case "sqlserver":
		p.createTableSql = fmt.Sprintf(createTableSqlserver, p.tableName)
		p.insertRowSql = fmt.Sprintf(insertRowSqlserver, p.tableName)
	}
}

func (p *Adapter) createTable()error{
	_, err := p.db.ExecContext(p.ctx, p.createTableSql)
	return err
}
// 彻底的清空表!(此操作不可逆!)
func (p *Adapter)truncateTable()error{
	_, err := p.db.ExecContext(p.ctx, p.truncateTableSql)
	return err
}

func (p *Adapter)tableExist()bool{
	_, err := p.db.ExecContext(p.ctx, p.tableExistSql)
	return err == nil
}

// 往 casbin_rule表中批量插入记录!
// truncate table casbin_rule
// insert into casbin_rule (p_type,v0,v1,v2,v3,v4,v5) values(?,?,?,?,?,?,?)"
func (p *Adapter)InsertBatch(list []CasbinRule)(err error){
	rules := make([][]interface{},len(list)) //初始化一个二元数组
	for i,rule := range list { // 操作每一条 casbin_rule记录!
		args := p.genArgs(rule.PType,[]string{rule.V0,rule.V1,rule.V2,rule.V3,rule.V4,rule.V5})
		rules[i] = args
	}

	if err =p.truncateTable();err !=nil {
		return
	}
	tx, err := p.db.BeginTx(p.ctx, nil)
	if err != nil {
		return
	}

	var (
		action string
		stmt *sql.Stmt
	)

	if stmt, err = tx.PrepareContext(p.ctx, p.insertRowSql); err != nil {
		action = "prepare context"
		goto Rollback //回滚事务(就是撤销当前事务操作)
	}

	for _, rule := range rules { //往 casbin_rule表中批量插入记录!
		if _, err =stmt.ExecContext(p.ctx,rule...); err != nil {
			action = "stmt exec"
			goto Rollback
		}
	}
	// 将当前连接放回连接池!
	if err = stmt.Close(); err != nil {
		action = "stmt close"
		goto Rollback
	}

	if err = tx.Commit(); err !=nil {
		action = "commit"
		goto Rollback
	}
	return

// 这是公共的逻辑!
Rollback:
	if err1 :=tx.Rollback(); err1 != nil {
		err = fmt.Errorf("%s err: %v, rollback err: %v",action,err,err1)
	}
	return
}

func (p *Adapter)truncateAndInsertRows(rules [][]interface{})(err error){
	if err =p.truncateTable();err !=nil {
		return
	}
	tx, err := p.db.BeginTx(p.ctx, nil)
	if err != nil {
		return
	}

	var (
		action string
		stmt *sql.Stmt
	)

	if stmt, err = tx.PrepareContext(p.ctx, p.insertRowSql); err != nil {
		action = "prepare context"
		goto Rollback //回滚事务(就是撤销当前事务操作)
	}

	for _, rule := range rules { //往 casbin_rule表中批量插入记录!
		if _, err =stmt.ExecContext(p.ctx,rule...); err != nil {
			action = "stmt exec"
			goto Rollback
		}
	}
	// 将当前连接放回连接池!
	if err = stmt.Close(); err != nil {
		action = "stmt close"
		goto Rollback
	}

	if err = tx.Commit(); err !=nil {
		action = "commit"
		goto Rollback
	}
	return

	// 这是公共的逻辑!
Rollback:
	if err1 :=tx.Rollback(); err1 != nil {
		err = fmt.Errorf("%s err: %v, rollback err: %v",action,err,err1)
	}
	return
}

func (p *Adapter)deleteRows(query string,args ...interface{}) error {
	switch p.db.DriverName() {
	case "sqlite3", "mysql":
	default:
		query = p.db.Rebind(query)
	}
	_, err := p.db.ExecContext(p.ctx,query,args...)
	return err
}

// 清空 casbin_rule中所有记录
// delete from casbin_rule
func (p *Adapter)Delete() error {
	query := p.deleteAllSql
	switch p.db.DriverName() {
	case "sqlite3", "mysql":
	default:
		query = p.db.Rebind(query)
	}
	_, err := p.db.ExecContext(p.ctx,query)
	return err
}

// 根据 p_type 字段删除 casbin_rule中记录
// delete from casbin_rule where p_type = ?
func (p *Adapter)DeleteByPType(pType ...interface{}) error {
	query := p.deleteByArgSql
	switch p.db.DriverName() {
	case "sqlite3", "mysql":
	default:
		query = p.db.Rebind(query)
	}
	_, err := p.db.ExecContext(p.ctx,query,pType...)
	return err
}
// 查询 casbin_rule中所有记录!
// select * from casbin_rule
func (p *Adapter)Select()([]*CasbinRule,error){
	lines := make([]*CasbinRule,0)
	return lines,p.db.SelectContext(p.ctx,&lines,p.selectAllSql)
}

// 查询 casbin_rule中所有记录!
func (p *Adapter)selectRows(query string,args ...interface{})([]*CasbinRule,error){
	lines := make([]*CasbinRule,0)
	if len(args) == 0 {
		return lines,p.db.SelectContext(p.ctx,&lines,query)
	}
	switch p.db.DriverName() {
	case "sqlite3","mysql"://如果是这样,则不做任何操作!
	default:
		query = p.db.Rebind(query)//重新绑定查询语句(主要是将SQL占位符转换成 ?)
	}
	return lines,p.db.SelectContext(p.ctx,&lines,query,args...)
}

func (p *Adapter)SelectWhereIn(filter *Filter)(lines []*CasbinRule,err error){
	var sqlBuf bytes.Buffer // 这是一个集装箱容器,可以存数据,取(数据)
	sqlBuf.Grow(64) //指定缓冲区的容量
	sqlBuf.WriteString(p.selectWhereSql)
	args := make([]interface{}, 0, 4)

	hasInCond := false

	for _, col := range [7]struct {
		name string
		arg  []string
	}{
		{"p_type", filter.PType},
		{"v0", filter.V0},
		{"v1", filter.V1},
		{"v2", filter.V2},
		{"v3", filter.V3},
		{"v4", filter.V4},
		{"v5", filter.V5},
	}{
		l := len(col.arg)
		if l == 0 {
			continue
		}

		switch sqlBuf.Bytes()[sqlBuf.Len()-1] {
		case '?', ')':
			sqlBuf.WriteString(" AND ")
		}

		sqlBuf.WriteString(col.name)

		if l == 1 {
			sqlBuf.WriteString(" = ?")
			args = append(args, col.arg[0])
		} else {
			sqlBuf.WriteString(" IN (?)")
			args = append(args, col.arg)

			hasInCond = true
		}
	}

	var query string

	if hasInCond {
		if query, args, err = sqlx.In(sqlBuf.String(), args...); err != nil {
			return
		}
	} else {
		query = sqlBuf.String()
	}

	return p.selectRows(query, args...)
}

// genArgs 从pType和rule自动生成参数
func (Adapter) genArgs(pType string, rule []string) []interface{} {
	list := len(rule)

	args := make([]interface{}, 7)
	args[0] = pType

	for i := 0; i < list; i++ {
		args[i+1] = rule[i]
	}

	for i := list + 1; i < 7; i++ {
		args[i] = ""
	}
	return args
}

// loadPolicyLine  从数据库中加载一条策略规则到模型实体中!(从数据库中读取记录)
func (Adapter) loadPolicyLine(line *CasbinRule, model model.Model) {
	if line == nil {
		return
	}

	var lineBuf bytes.Buffer

	lineBuf.Grow(64)
	lineBuf.WriteString(line.PType)

	args := [6]string{line.V0, line.V1, line.V2, line.V3, line.V4, line.V5}
	for _, arg := range args {
		if arg != "" {
			lineBuf.WriteByte(',')
			lineBuf.WriteString(arg)
		}
	}
	persist.LoadPolicyLine(lineBuf.String(), model)
}

// LoadPolicy  从存储(例如:数据库 casbin_rule表)中加载所有的策略规则到 model.Model 这个容器中!.
func (p *Adapter) LoadPolicy(model model.Model) error {
	lines, err := p.selectRows(p.selectAllSql)
	if err != nil {
		return err
	}

	for _, line := range lines {
		p.loadPolicyLine(line, model)
	}
	return nil
}

// SavePolicy  将策略规则保存到存储(例如:数据库 casbin_rule表)中。
func (p *Adapter) SavePolicy(model model.Model) error {
	args := make([][]interface{}, 0, 64)

	for ptype, ast := range model["p"] {
		for _, rule := range ast.Policy {
			arg := p.genArgs(ptype, rule)
			args = append(args, arg)
		}
	}

	for ptype, ast := range model["g"] {
		for _, rule := range ast.Policy {
			arg := p.genArgs(ptype, rule)
			args = append(args, arg)
		}
	}

	return p.truncateAndInsertRows(args)
}

// AddPolicy  向存储(数据库表)中添加一个策略规则。
func (p *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
	args := p.genArgs(ptype, rule)

	_, err := p.db.ExecContext(p.ctx, p.insertRowSql, args...)

	return err
}

// RemovePolicy  从存储(数据库表)中删除策略规则。
func (p *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
	var sqlBuf bytes.Buffer

	sqlBuf.Grow(64)
	sqlBuf.WriteString(p.deleteByArgSql)

	args := make([]interface{}, 0, 4)
	args = append(args, ptype)

	for idx, arg := range rule {
		if arg != "" {
			sqlBuf.WriteString(" AND v")
			sqlBuf.WriteString(strconv.Itoa(idx))
			sqlBuf.WriteString(" = ?")

			args = append(args, arg)
		}
	}

	return p.deleteRows(sqlBuf.String(), args...)
}

// RemoveFilteredPolicy  从存储中删除与过滤器匹配的策略规则。
func (p *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
	var sqlBuf bytes.Buffer

	sqlBuf.Grow(64)
	sqlBuf.WriteString(p.deleteByArgSql)

	args := make([]interface{}, 0, 4)
	args = append(args, ptype)

	var value string

	l := fieldIndex + len(fieldValues)

	for idx := 0; idx < 6; idx++ {
		if fieldIndex <= idx && idx < l {
			value = fieldValues[idx-fieldIndex]

			if value != "" {
				sqlBuf.WriteString(" AND v")
				sqlBuf.WriteString(strconv.Itoa(idx))
				sqlBuf.WriteString(" = ?")

				args = append(args, value)
			}
		}
	}

	return p.deleteRows(sqlBuf.String(), args...)
}

// IsFiltered  如果已过滤加载的策略规则，则返回true!.
func (p *Adapter) IsFiltered() bool {
	return p.isFiltered
}

// LoadFilteredPolicy  与filter.filterPtr匹配的加载策略规则必须是一个指针!
func (p *Adapter) LoadFilteredPolicy(model model.Model, filterPtr interface{}) error {
	if filterPtr == nil {
		return p.LoadPolicy(model)
	}

	filter, ok := filterPtr.(*Filter)
	if !ok {
		return errors.New("invalid filter type")
	}

	lines, err := p.SelectWhereIn(filter)
	if err != nil {
		return err
	}

	for _, line := range lines {
		p.loadPolicyLine(line, model)
	}

	p.isFiltered = true

	return nil
}
// 返回一个 Adapter指针,同时在指定的数据库中创建一张表! 如果<tableName>为空,创建的表为:casbin_rule
// 一个 *Adapter 对象 能对 casbin_rule 这张表执行:增、删、改、查!
func NewAdapter(db *sqlx.DB,tableName string)(*Adapter,error){
	return NewAdapterByContext(context.Background(),db,tableName)
}

func NewAdapterByContext(ctx context.Context,db *sqlx.DB,tableName string)(*Adapter,error){
	if db == nil {
		return nil,errors.New(" 请输入有效的 *sqlx.DB 指针!")
	}
	// 通过上下文来 Ping一下
	err := db.PingContext(ctx)
	if err != nil {
		return nil, err
	}

	switch db.DriverName() {
	case "oci8","ora","oracle":
		return nil, errors.New(" 当前分支不支持 oracle数据库,请移步到 'oracle' 分支!" )
	}

	if tableName == "" {
		tableName = defaultTableName
	}

	obj := Adapter{
		db: db,
		ctx: ctx,
		tableName: tableName,
	}

	obj.initAdapter()

	if !obj.tableExist() {
		if err = obj.createTable();err != nil {
			return nil, err
		}
	}
	return &obj, nil
}
